﻿// -----------------------------------------------------------------------
// <copyright file="BinaryReaderStream.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Utilities
{
    using System;
    using System.IO;

    /// <summary>
    /// A BinaryReader with a stream interface. It has a "priviledged" connection
    /// with <seealso cref="MemoryBuffer">MemoryBuffer</seealso>
    /// </summary>
    public class BinaryReaderStream : Stream
    {
        /// <summary>
        /// 
        /// </summary>
        private int position;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        public BinaryReaderStream(MemoryBuffer buffer)
        {
            this.Buffer = buffer;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        public BinaryReaderStream(Stream stream)
        {
            this.BinaryStream = new BinaryReader(stream);
        }

        /// <summary>
        /// 
        /// </summary>
        public MemoryBuffer Buffer { get; protected set; }

        /// <summary>
        /// 
        /// </summary>
        public BinaryReader BinaryStream { get; protected set; }

        /// <summary>
        /// 
        /// </summary>
        public Stream Stream
        {
            get
            {
                return this.BinaryStream != null ? this.BinaryStream.BaseStream : null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override long Position
        {
            get
            {
                if (this.BinaryStream == null)
                {
                    return this.position;
                }

                return this.Stream.Position;
            }

            set
            {
                if (this.BinaryStream == null)
                {
                    if (value < 0 || value > this.Buffer.Length)
                    {
                        throw new ArgumentOutOfRangeException("value");
                    }

                    this.position = checked((int)value);
                }
                else
                {
                    this.Stream.Position = value;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override long Length
        {
            get
            {
                if (this.BinaryStream == null)
                {
                    return this.Buffer.Length;
                }

                return this.Stream.Length;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override bool CanRead
        {
            get
            {
                if (this.BinaryStream == null)
                {
                    return true;
                }

                return this.Stream.CanRead;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override bool CanSeek
        {
            get
            {
                if (this.BinaryStream == null)
                {
                    return true;
                }

                return this.Stream.CanSeek;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override bool CanWrite
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override void Flush()
        {
            if (this.BinaryStream != null)
            {
                this.Stream.Flush();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="origin"></param>
        /// <returns></returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            if (this.BinaryStream != null)
            {
                return this.Stream.Seek(offset, origin);
            }

            switch (origin)
            {
                case SeekOrigin.Begin:
                    this.Position = offset;
                    break;

                case SeekOrigin.Current:
                    this.Position += offset;
                    break;

                case SeekOrigin.End:
                    this.Position = this.Buffer.Length - offset;
                    break;

                default:
                    throw new ArgumentException("origin");
            }

            return this.Position;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public sbyte ReadSByte()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadSByte(this.position);
                this.position += sizeof(sbyte);
                return value;
            }

            return this.BinaryStream.ReadSByte();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int ReadByte()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadByte(this.position);
                this.position += sizeof(byte);
                return value;
            }

            return this.BinaryStream.ReadByte();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public byte ReadFixedByte()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadByte(this.position);
                this.position += sizeof(byte);
                return value;
            }

            return this.BinaryStream.ReadByte();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool ReadBoolean()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadBoolean(this.position);
                this.position += sizeof(bool);
                return value;
            }

            return this.BinaryStream.ReadBoolean();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public short ReadInt16()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadInt16(this.position);
                this.position += sizeof(short);
                return value;
            }

            return this.BinaryStream.ReadInt16();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public ushort ReadUInt16()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadUInt16(this.position);
                this.position += sizeof(ushort);
                return value;
            }

            return this.BinaryStream.ReadUInt16();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public char ReadChar()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadChar(this.position);
                this.position += sizeof(char);
                return value;
            }

            return this.BinaryStream.ReadChar();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public int ReadInt32()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadInt32(this.position);
                this.position += sizeof(int);
                return value;
            }

            return this.BinaryStream.ReadInt32();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public uint ReadUInt32()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadUInt32(this.position);
                this.position += sizeof(uint);
                return value;
            }

            return this.BinaryStream.ReadUInt32();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public long ReadInt64()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadInt64(this.position);
                this.position += sizeof(long);
                return value;
            }

            return this.BinaryStream.ReadInt64();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public ulong ReadUInt64()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadUInt64(this.position);
                this.position += sizeof(ulong);
                return value;
            }

            return this.BinaryStream.ReadUInt64();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public float ReadSingle()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadSingle(this.position);
                this.position += sizeof(float);
                return value;
            }

            return this.BinaryStream.ReadSingle();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public double ReadDouble()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadDouble(this.position);
                this.position += sizeof(double);
                return value;
            }

            return this.BinaryStream.ReadDouble();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IntPtr ReadIntPtr()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadIntPtr(this.position);
                this.position += IntPtr.Size;
                return value;
            }

            return this.BinaryStream.ReadIntPtr();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public UIntPtr ReadUIntPtr()
        {
            if (this.BinaryStream == null)
            {
                var value = this.Buffer.ReadUIntPtr(this.position);
                this.position += UIntPtr.Size;
                return value;
            }

            return this.BinaryStream.ReadUIntPtr();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(sbyte[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(sbyte);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2 * sizeof(sbyte);
                return count;
            }

            return this.BinaryStream.ReadFixed((byte[])(Array)buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int ReadVariable(byte[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                count = Math.Min(count, this.Buffer.Length - this.position);
                int count2 = count;
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(byte);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.ReadFixed(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(bool[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(bool);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(short[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(short);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(ushort[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(ushort);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(char[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(char);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(int[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(int);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(uint[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(uint);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(long[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(long);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(ulong[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(ulong);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(float[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(float);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(double[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeof(double);
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(IntPtr[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * IntPtr.Size;
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(UIntPtr[] buffer, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * UIntPtr.Size;
                this.Buffer.Read(this.position, buffer, offset, count);
                this.position += count2;
                return count;
            }

            return this.BinaryStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="sizeElement"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int Read(Array buffer, int sizeElement, int offset, int count)
        {
            if (this.BinaryStream == null)
            {
                int count2 = count * sizeElement;
                this.Buffer.Read(this.position, buffer, sizeElement, offset, count);
                this.position += count2 * sizeElement;
                return count;
            }

            return this.BinaryStream.Read(buffer, sizeElement, offset, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            this.Buffer = null;
            this.BinaryStream = null;
        }
    }
}